package ru.amse.baltijsky.javascheme.frames.main;

import ru.amse.baltijsky.javascheme.exporter.image.ImageExporterFactory;
import ru.amse.baltijsky.javascheme.frames.image.ImageViewPanel;
import ru.amse.baltijsky.javascheme.importer.IImporter;
import ru.amse.baltijsky.javascheme.importer.IImporterFactory;
import ru.amse.baltijsky.javascheme.util.Util;
import ru.amse.baltijsky.javascheme.util.VisConst;
import static ru.amse.baltijsky.javascheme.util.VisConst.PREFERRED_SCROLLER_HEIGHT;
import static ru.amse.baltijsky.javascheme.util.VisConst.PREFERRED_SCROLLER_WIDTH;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.*;

/**
 * Displays the main frame of the GUI for the JavaScheme application.
 */
public class MainFrame extends JFrame {
    /**
     * Action for the Open File button (used in menu, toolbar).
     */
    private class OpenButtonAction extends AbstractAction {
        public OpenButtonAction(String name, Icon icon) {
            super(name, icon);
        }

        public void actionPerformed(ActionEvent e) {
            showOpenDialog();
        }
    }

    /**
     * Action for the Save To File button (used in menu, toolbar).
     */
    private class SaveButtonAction extends AbstractAction {
        private SaveButtonAction(String name, Icon icon) {
            super(name, icon);
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
            showSaveDialog();
        }
    }

    /**
     * Action for the Close Current Tab button (used in menu).
     */
    private class CloseTabButtonAction extends AbstractAction {
        private CloseTabButtonAction(String name, Icon icon) {
            super(name, icon);
            setEnabled(false);
            putValue(MNEMONIC_KEY, KeyEvent.VK_C);
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.VK_ALT));
        }

        public void actionPerformed(ActionEvent e) {
            tabs.remove(tabs.getSelectedComponent());
        }
    }

    /**
     * Action for the Set Subtrees Frame Decoration (used in menu).
     */
    private class SubtreeFramesDecorationAction extends AbstractAction {
        private SubtreeFramesDecorationAction(String name) {
            super(name);
            putValue(MNEMONIC_KEY, KeyEvent.VK_F);
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.getKeyText(KeyEvent.VK_F2)));
        }

        public void actionPerformed(ActionEvent e) {
            for (int i = 0; i < tabs.getTabCount(); ++i) {
                ImageViewPanel p = (ImageViewPanel) (((JScrollPane) tabs.getComponentAt(i))).getViewport().getView();
                p.getExporter().setFramesSubtreeDecoration(setFramesDecorationItem.getState());
            }
            if (tabs.getSelectedComponent() != null) {
                tabs.getSelectedComponent().repaint();
            }
        }
    }

    private final OpenButtonAction openButtonAction = new OpenButtonAction("Open file...", createIcon("Open16"));
    private final SaveButtonAction saveButtonAction = new SaveButtonAction("Export to image ... ", createIcon("SaveAs16"));
    private final CloseTabButtonAction closeTabButtonAction = new CloseTabButtonAction("Close current tab", null);
    private final SubtreeFramesDecorationAction subtreeFramesDecorationAction = new SubtreeFramesDecorationAction("Enable subtrees frames");
    private final JFileChooser openFileChooser = new JFileChooser();
    private final JFileChooser saveFileChooser = new JFileChooser();
    private final ChooseClassMethodDialog classMethodDialog;
    private final IImporter importer;
    private final JTabbedPane tabs = new JTabbedPane();
    private JCheckBoxMenuItem setFramesDecorationItem;

    /**
     * Runs the GUI thread with the frame.
     *
     * @param importerFactory importer factory to create schema trees
     */
    public static void runFrame(final IImporterFactory importerFactory) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                setLF();
                new MainFrame(importerFactory);
            }
        });
    }

    /**
     * Runs the GUI thread with the frame and opens scheme for the specified class method.
     *
     * @param importerFactory importer factory to create schema trees
     * @param srcPath         path to the source file to be opened
     * @param className       name of the class the method of which to import
     * @param methodName      name of the method to be imported
     * @throws java.io.FileNotFoundException if the source file does not exist in the given path
     */

    public static void runFrame(final IImporterFactory importerFactory, String srcPath, final String className, final String methodName) throws FileNotFoundException {
        final Reader in = new BufferedReader(new FileReader(srcPath));
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    setLF();
                    MainFrame mainFrame = new MainFrame(importerFactory);
                    mainFrame.createAndShowSchema(in, className, methodName);
                } finally {
                    if (in != null) {
                        try {
                            in.close();
                        } catch (IOException e) {
                        }
                    }
                }
            }
        });
    }

    /**
     * Sets the application look and feel.
     */
    private static void setLF() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }

    /**
     * Creates a main frame able to import schema trees from source files using the importers provided by the given factory.
     *
     * @param importerFactory the factory to create schema tree importers for the application
     */
    public MainFrame(IImporterFactory importerFactory) {
        super("JavaScheme");
        importer = importerFactory.createImporter("java");
        setLayout(new BorderLayout());
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        setupOpenDialog();
        setupSaveDialog();
        classMethodDialog = new ChooseClassMethodDialog(this);
        createMenu();
        createToolbar();
        add(tabs, BorderLayout.CENTER);
        tabs.setFocusable(false);
        tabs.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (tabs.getTabCount() == 0) {
                    closeTabButtonAction.setEnabled(false);
                    saveButtonAction.setEnabled(false);
                }
            }
        });

        setPreferredSize(new Dimension(VisConst.PREFERRED_SCROLLER_WIDTH, VisConst.PREFERRED_SCROLLER_HEIGHT));
        setLocationByPlatform(true);
        pack();
        setVisible(true);
    }

    private void setupOpenDialog() {
        openFileChooser.setCurrentDirectory(new File(System.getProperty("user.dir")));
        openFileChooser.addChoosableFileFilter(new FileNameExtensionFilter("*.java (java source files)", "java"));
    }


    private void setupSaveDialog() {
        saveFileChooser.setCurrentDirectory(new File(System.getProperty("user.dir")));
        saveFileChooser.addChoosableFileFilter(new FileNameExtensionFilter("GIF (*.gif)", "gif"));
        saveFileChooser.addChoosableFileFilter(new FileNameExtensionFilter("BMP (*.bmp)", "bmp"));
        saveFileChooser.addChoosableFileFilter(new FileNameExtensionFilter("PNG (*.png)", "png"));
        saveFileChooser.addChoosableFileFilter(new FileNameExtensionFilter("JPEG (*.jpg; *.jpeg)", "jpg", "jpeg"));
        saveFileChooser.setAcceptAllFileFilterUsed(false);
    }

    private void createMenu() {
        JMenuBar menuBar = new JMenuBar();

        JMenu file = new JMenu("File");
        file.setMnemonic(KeyEvent.VK_F);
        file.getAccessibleContext().setAccessibleDescription("File tasks");
        menuBar.add(file);

        {
            JMenuItem openItem = new JMenuItem(openButtonAction);
            openItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.VK_ALT));
            openItem.setMnemonic(KeyEvent.VK_O);
            file.add(openItem);

            JMenuItem saveItem = new JMenuItem(saveButtonAction);
            saveItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.VK_ALT));
            saveItem.setMnemonic(KeyEvent.VK_S);
            file.add(saveItem);

            file.addSeparator();
            JMenuItem exitItem = new JMenuItem("Exit");
            exitItem.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    System.exit(0);
                }
            });
            exitItem.setMnemonic(KeyEvent.VK_X);
            file.add(exitItem);
        }

        JMenu view = new JMenu("View");
        view.setMnemonic(KeyEvent.VK_V);
        view.getAccessibleContext().setAccessibleDescription("View options");
        setFramesDecorationItem = new JCheckBoxMenuItem(subtreeFramesDecorationAction);
        setFramesDecorationItem.setSelected(true);
        view.add(setFramesDecorationItem);

        menuBar.add(view);

        JMenu window = new JMenu("Window");
        window.setMnemonic(KeyEvent.VK_W);
        window.getAccessibleContext().setAccessibleDescription("Windows tasks");
        window.add(new JMenuItem(closeTabButtonAction));
        menuBar.add(window);

        setJMenuBar(menuBar);
    }

    private void createToolbar() {
        JToolBar toolBar = new JToolBar("Main");

        JButton openButton = new JButton(openButtonAction);
        openButton.setText("");
        openButton.setIcon(createIcon("Open24"));
        openButton.setToolTipText("Open file");
        openButton.setFocusable(false);
        toolBar.add(openButton);

        JButton saveButton = new JButton(saveButtonAction);
        saveButton.setText("");
        saveButton.setIcon(createIcon("SaveAs24"));
        saveButton.setToolTipText("Save image");
        saveButton.setFocusable(false);
        toolBar.add(saveButton);

        add(toolBar, BorderLayout.PAGE_START);
    }

    /**
     * Starts the open file dialog.
     */
    private void showOpenDialog() {
        int ret = openFileChooser.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION) {
            try {
                classMethodDialog.showDialog(importer.getClasses(new BufferedReader(new FileReader(openFileChooser.getSelectedFile()))), new IMethodGetter() {
                    public String[] getMethodsByClass(String className) {
                        try {
                            return importer.getMethods(new BufferedReader(new FileReader(openFileChooser.getSelectedFile())), className);
                        } catch (IOException e) {
                            showError(e.getMessage());
                        }
                        return new String[0];
                    }
                });
                // now we've got the class and the method; we can start building schema tree
                String className = classMethodDialog.getSelectedClass();
                String methodName = classMethodDialog.getSelectedMethod();
                if (classMethodDialog.isAccepted() && !className.equals("") && !methodName.equals("")) {
                    createAndShowSchema(new BufferedReader(new FileReader(openFileChooser.getSelectedFile())), className, methodName);
                }
            } catch (FileNotFoundException e) {
                showError("Cannot open the specified file for reading.");
            } catch (IOException e) {
                showError(e.getMessage());
            }
        }
    }

    /**
     * Creates a schema and displays in a new tab.
     *
     * @param in         input reader with the source code
     * @param className  name of the class to create schema
     * @param methodName name of the method to create schema
     */
    private void createAndShowSchema(Reader in, String className, String methodName) {
        try {
            ImageViewPanel viewPanel = new ImageViewPanel((new ImageExporterFactory()).createExporter(importer.loadTree(in, className, methodName)));
            viewPanel.getExporter().setFramesSubtreeDecoration(setFramesDecorationItem.getState());
            JScrollPane scroller = new JScrollPane(viewPanel);
            scroller.setPreferredSize(new Dimension(PREFERRED_SCROLLER_WIDTH, PREFERRED_SCROLLER_HEIGHT));

            tabs.addTab(className + "." + methodName, scroller);
            tabs.setSelectedComponent(scroller);
            tabs.setTabComponentAt(tabs.indexOfComponent(scroller), new ButtonTabComponent(tabs));
            closeTabButtonAction.setEnabled(true);

            //pack();
            viewPanel.start();
            viewPanel.revalidate();

            saveButtonAction.setEnabled(true);
        } catch (IOException e) {
            showError(e.getMessage());
        }
    }

    private void showSaveDialog() {
        int ret = saveFileChooser.showSaveDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION) {
            File selectedFile = saveFileChooser.getSelectedFile();
            String[] extensions = ((FileNameExtensionFilter) saveFileChooser.getFileFilter()).getExtensions();
            selectedFile = ensureExt(selectedFile, extensions);

            // check writing over existing file
            boolean writingOnOldFile = false;
            if (selectedFile.exists()) {
                String text = String.format("%s already exists.%nDo you want to replace it?", selectedFile.getName());
                int answer = JOptionPane.showConfirmDialog(this, text, "Confirm Save", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
                if (answer == JOptionPane.NO_OPTION) {
                    return;
                }
                writingOnOldFile = true;
            }

            // export to file
            try {
                ImageViewPanel selectedPanel = (ImageViewPanel) (((JScrollPane) tabs.getSelectedComponent()).getViewport().getView());
                selectedPanel.getExporter().exportToFile(selectedFile, extensions[0]);
            } catch (IOException e) {
                showError("Could not save image to file: " + e.getMessage());
                if (!writingOnOldFile) {
                    selectedFile.delete();
                }
            }
        }
    }

    /**
     * Ensures that the file has the extension from the extension list. If not from the list, replaces the file with
     * the file with the first extension from the list and returns it.
     *
     * @param selectedFile file to check the extension
     * @param extensions   the list of expected extensions
     * @return the file with the extension from the list of expected extensions
     */
    private File ensureExt(File selectedFile, String[] extensions) {
        String actualExt = Util.getExtension(selectedFile);
        boolean selectedExtensionMatches = false;
        for (String s : extensions) {
            if (s.equals(actualExt)) {
                selectedExtensionMatches = true;
            }
        }
        if (!selectedExtensionMatches) {
            File oldFile = selectedFile;
            selectedFile = new File(selectedFile.getAbsolutePath() + "." + extensions[0]);
            oldFile.delete();
        }
        return selectedFile;
    }

    private void showError(String desc) {
        JOptionPane.showMessageDialog(this, desc, "Error", JOptionPane.ERROR_MESSAGE);
    }

    /**
     * Returns an ImageIcon, or null if the path was invalid.
     *
     * @param imageName image name
     * @return the icon, or null if the icon was not found.
     */
    private static ImageIcon createIcon(String imageName) {
        String imgLocation = "/icons/" + imageName + ".gif";
        java.net.URL imageURL = MainFrame.class.getResource(imgLocation);
        if (imageURL == null) {
            System.err.println("Resource not found: " + imgLocation);
            return null;
        } else {
            return new ImageIcon(imageURL);
        }
    }
}


